package com.coderworm.netty;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleStateHandler;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * Netty版RTCM客户端完整实现
 * 功能：
 * 1. 自动生成符合RTCM格式的测试文件（含0xD3帧头）
 * 2. 配置服务端连接参数（IP、端口、超时时间）
 * 3. 初始化Netty客户端环境，集成RtcmClientHandler处理通信逻辑
 * 4. 支持异常重试（可选）、连接状态监控
 */
public class RtcmNettyClient {
    // ======================== 可配置参数（根据实际场景修改） ========================
    /** 服务端IP地址（本地测试用localhost，远程部署填服务端实际IP） */
    private static final String SERVER_IP = "localhost";
    /** 服务端监听端口（必须与服务端RtcmNettyServer配置一致） */
    private static final int SERVER_PORT = 8888;
    /** 连接超时时间（单位：秒，避免长时间阻塞） */
    private static final int CONNECT_TIMEOUT_SECONDS = 60;
    /** 重试次数（连接失败时自动重试，0表示不重试） */
    private static final int RETRY_COUNT = 2;
    /** 重试间隔（单位：秒，每次重试前等待时间） */
    private static final int RETRY_INTERVAL_SECONDS = 3;

    /** 客户端身份信息（需与服务端AUTH_USERS匹配） */
    private static final String AUTH_USERNAME = "rtcm_device_001";
    private static final String AUTH_PASSWORD = "SecurePwd@2025";
    private static final String PASSWORD_SALT = "RtcmSecure2025_#"; // 与服务端SALT完全一致

    /** 测试文件配置（无实际文件时自动生成） */
    private static final String RTCM_TEST_FILE_PATH = "rtcm_test_data3.bin";
    private static final int TEST_FILE_SIZE_KB = 60; // 测试文件大小（20KB，可调整）

    // ==============================================================================

    /** 客户端事件循环组（负责IO事件处理，单例复用） */
    private EventLoopGroup workerGroup;
    /** 标记客户端是否正在运行 */
    private volatile boolean isRunning = false;

    public static void main(String[] args) {
        // 启动客户端（通过try-with-resources确保资源释放）
        try  {
            RtcmNettyClient client = new RtcmNettyClient();
            // 1. 准备测试文件（不存在则生成）
            client.prepareTestRtcmFile();
            // 2. 连接服务端并发送文件（支持重试）
            client.connectAndSendWithRetry();
        } catch (Exception e) {
            System.err.println("[客户端] 整体执行异常：" + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 构造方法：初始化客户端状态
     */
    public RtcmNettyClient() {
        this.workerGroup = new NioEventLoopGroup(1); // 客户端仅需1个线程组（减少资源占用）
        this.isRunning = true;
    }

    /**
     * 准备测试RTCM文件：不存在则生成符合格式的测试文件
     * @throws IOException 生成文件失败时抛出异常
     */
    private void prepareTestRtcmFile() throws IOException {
        File testFile = new File(RTCM_TEST_FILE_PATH);
        if (testFile.exists() && testFile.isFile() && testFile.length() > 0) {
            System.out.println("[客户端] 测试文件已存在：" + testFile.getAbsolutePath());
            System.out.println("[客户端] 文件大小：" + testFile.length() + " 字节");
            return;
        }

        // 生成RTCM测试文件（含0xD3帧头，模拟真实二进制数据）
        int totalSize = TEST_FILE_SIZE_KB * 1024;
        try (FileOutputStream fos = new FileOutputStream(testFile)) {
            // 1. 写入RTCM标准帧头（0xD3，必须包含，否则服务端判定为非法文件）
            fos.write(0xD3);
            // 2. 生成剩余字节的随机二进制数据（模拟RTCM观测值、导航电文）
            byte[] randomData = new byte[totalSize - 1];
            new Random().nextBytes(randomData);
            fos.write(randomData);

            System.out.println("[客户端] 测试文件生成成功：");
            System.out.println("  - 路径：" + testFile.getAbsolutePath());
            System.out.println("  - 大小：" + totalSize + " 字节（" + TEST_FILE_SIZE_KB + "KB）");
            System.out.println("  - 帧头：0xD3（RTCM标准帧头）");
        }
    }

    /**
     * 带重试机制的连接与发送：连接失败时自动重试指定次数
     * @throws InterruptedException 线程等待被中断时抛出
     */
    private void connectAndSendWithRetry() throws InterruptedException {
        int currentRetry = 0;
        while (isRunning && currentRetry <= RETRY_COUNT) {
            try {
                System.out.println("\n[客户端] 第" + (currentRetry + 1) + "次尝试连接服务端（" + SERVER_IP + ":" + SERVER_PORT + "）");
                // 执行实际的连接与发送逻辑
                connectAndSend();
                // 发送成功，跳出重试循环
                break;
            } catch (Exception e) {
                currentRetry++;
                if (currentRetry > RETRY_COUNT) {
                    // 重试次数用尽，抛出异常
                    throw new RuntimeException("[客户端] 所有重试均失败（共" + RETRY_COUNT + "次），原因：" + e.getMessage(), e);
                }
                // 重试前等待指定时间
                System.err.println("[客户端] 第" + currentRetry + "次连接失败，" + RETRY_INTERVAL_SECONDS + "秒后重试，原因：" + e.getMessage());
                TimeUnit.SECONDS.sleep(RETRY_INTERVAL_SECONDS);
            }
        }
    }

    /**
     * 核心逻辑：初始化Netty客户端，连接服务端并发送文件
     * @throws Exception 连接或发送过程中发生异常
     */
    private void connectAndSend() throws Exception {
        try {
            // 1. 初始化Netty客户端启动器
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(workerGroup)
                    // 配置客户端通道类型（NIO非阻塞通道）
                    .channel(NioSocketChannel.class)
                    // 配置TCP参数：禁用Nagle算法（小数据传输更及时）
                    .option(ChannelOption.TCP_NODELAY, true)
                    // 配置TCP参数：开启保活机制（避免僵尸连接）
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    // 配置连接超时时间
                    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, (int) TimeUnit.SECONDS.toMillis(CONNECT_TIMEOUT_SECONDS))
                    // 配置通道初始化器（添加处理器链）
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            // 处理器链顺序：按执行顺序添加（先执行前面的处理器）
                            // 1. 连接超时处理器：超过指定时间未连接成功则触发超时
                            ch.pipeline().addLast(new IdleStateHandler(CONNECT_TIMEOUT_SECONDS,CONNECT_TIMEOUT_SECONDS,CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS));
                            // 2. 核心业务处理器：身份验证、文件发送（集成之前的RtcmClientHandler）
                            ch.pipeline().addLast(new RtcmClientHandler(
                                    AUTH_USERNAME,
                                    AUTH_PASSWORD,
                                    PASSWORD_SALT,
                                    RTCM_TEST_FILE_PATH
                            ));
                        }
                    });

            // 2. 异步连接服务端（sync()阻塞等待连接完成）
            ChannelFuture future = bootstrap.connect(SERVER_IP, SERVER_PORT).sync();
            System.out.println("[客户端] 与服务端连接建立成功（" + SERVER_IP + ":" + SERVER_PORT + "）");

            // 3. 阻塞等待通道关闭（直到文件发送完成或连接异常关闭）
            future.channel().closeFuture().sync();
            System.out.println("[客户端] 通道已关闭，文件发送流程结束");

        } finally {
            // 标记客户端停止运行
            isRunning = false;
        }
    }

    /**
     * 关闭客户端资源（实现AutoCloseable接口，支持try-with-resources）
     * @throws Exception 资源关闭过程中发生异常
     */
    public void close() throws Exception {
        if (isRunning) {
            isRunning = false;
        }
        // 优雅关闭事件循环组（释放线程资源）
        if (workerGroup != null && !workerGroup.isShuttingDown()) {
            workerGroup.shutdownGracefully()
                    .syncUninterruptibly(); // 等待资源完全释放
            System.out.println("[客户端] 事件循环组已优雅关闭");
        }
    }
}